package simpledb;

import java.io.*;
import java.util.*;

/**
 * HeapFile is an implementation of a DbFile that stores a collection of tuples
 * in no particular order. Tuples are stored on pages, each of which is a fixed
 * size, and the file is simply a collection of those pages. HeapFile works
 * closely with HeapPage. The format of HeapPages is described in the HeapPage
 * constructor.
 * 
 * @see simpledb.HeapPage#HeapPage
 * @author Sam Madden
 */
public class HeapFile implements DbFile {

    private File file;
    private TupleDesc td;
    /**
     * Constructs a heap file backed by the specified file.
     * 
     * @param f
     *            the file that stores the on-disk backing store for this heap
     *            file.
     */
    public HeapFile(File f, TupleDesc td) {
        // some code goes here
        this.file = f;
        this.td = td;
    }

    /**
     * Returns the File backing this HeapFile on disk.
     * 
     * @return the File backing this HeapFile on disk.
     */
    public File getFile() {
        // some code goes here
        return file;
    }

    /**
     * Returns an ID uniquely identifying this HeapFile. Implementation note:
     * you will need to generate this tableid somewhere to ensure that each
     * HeapFile has a "unique id," and that you always return the same value for
     * a particular HeapFile. We suggest hashing the absolute file name of the
     * file underlying the heapfile, i.e. f.getAbsoluteFile().hashCode().
     * 
     * @return an ID uniquely identifying this HeapFile.
     */
    public int getId() {
        // some code goes here
        if(file == null)
          throw new UnsupportedOperationException("implement this");
        return file.getAbsolutePath().hashCode();
    }

    /**
     * Returns the TupleDesc of the table stored in this DbFile.
     * 
     * @return TupleDesc of this DbFile.
     */
    public TupleDesc getTupleDesc() {
        // some code goes here
        return td;
    }

    // see DbFile.java for javadocs
    /**
    从磁盘中读取一个页面
    此方法应该只能在bufferpool中调用，不然无法实现缓存功能
    RandomAccessFile给定一个文件起始处（以字节为单位），读取或写入字节
    使用pid的pageNumber得到当前pid在heapPage里的偏移
     */
    public Page readPage(PageId pid) {
        // some code goes here
        int pgno = pid.getPageNumber();
        int offset = pgno*BufferPool.getPageSize();
        byte[] data = new byte[BufferPool.getPageSize()];
        Page page = null;
        try (
                RandomAccessFile raf = new RandomAccessFile(file,"r");
        ) {
            raf.seek(offset);
            raf.read(data,0,BufferPool.getPageSize());
            page = new HeapPage((HeapPageId) pid,data);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return page;
    }

    // see DbFile.java for javadocs
    // 写入一个page
    public void writePage(Page page) throws IOException {
        // some code goes here
        // not necessary for lab1
        RandomAccessFile raf = new RandomAccessFile(file, "rw");
        //获得写入的page在整个file中的位置
        raf.seek(page.getId().getPageNumber()*BufferPool.getPageSize());
        raf.write(page.getPageData());
    }

    /**
     * Returns the number of pages in this HeapFile.
     */
    public int numPages() {
        // some code goes here
        int numPages = (int) (file.length()/BufferPool.getPageSize());
        if(file.length()%BufferPool.getPageSize()!=0)
            numPages++;
        return numPages;
    }

    // see DbFile.java for javadocs
    /**
       插入一个元组
       1.检索表中的所有页面，当存在有空闲位置时，直接插入。返回更新的page，标记更新页面为dirty
       2.如果已有页面都存满，开辟一个新page,使用writePage()写入，返回新建立的page，标记更新页面为dirty
     */
    public ArrayList<Page> insertTuple(TransactionId tid, Tuple t)
            throws DbException, IOException, TransactionAbortedException {
        // some code goes here
        int numPages = numPages();
        ArrayList<Page> affectedPages = new ArrayList<>();
        for(int i = 0;i<numPages();i++)
        {
            HeapPageId heapPageId = new HeapPageId(getId(),i);
            HeapPage page = (HeapPage) Database.getBufferPool().getPage(tid,heapPageId,Permissions.READ_WRITE);
            int numEmptySlots = page.getNumEmptySlots();
            if(numEmptySlots>0)
            {
                page.insertTuple(t);
                affectedPages.add(page);
                page.markDirty(true,tid);
                break;
            }
        }
        if(affectedPages.size()==0)
        {
             HeapPageId heapPageId = new HeapPageId(getId(),numPages);
             HeapPage blankpage = new HeapPage(heapPageId,HeapPage.createEmptyPageData());
             numPages++;
             writePage(blankpage);
            HeapPage new_page = (HeapPage) Database.getBufferPool().getPage(tid, heapPageId, Permissions.READ_WRITE);
            new_page.insertTuple(t);
            new_page.markDirty(true,tid);
            affectedPages.add(new_page);
        }
        return affectedPages;
        // not necessary for lab1
    }

    // see DbFile.java for javadocs
    /*
     删除一个给定元组
     */
    public ArrayList<Page> deleteTuple(TransactionId tid, Tuple t) throws DbException,
            TransactionAbortedException {
        // some code goes here
        ArrayList<Page> affectedPages = new ArrayList<Page>();
        RecordId recordId = t.getRecordId();
        PageId pageId = recordId.getPageId();
        HeapPage page = (HeapPage) Database.getBufferPool().getPage(tid, pageId, Permissions.READ_WRITE);
        page.deleteTuple(t);
        page.markDirty(true,tid);
        affectedPages.add(page);
        return affectedPages;
        // not necessary for lab1
    }

    // see DbFile.java for javadocs
    public DbFileIterator iterator(TransactionId tid) {
        // some code goes here
        return new HeapFileIterator(tid);
    }

    /**
    在一个heapFile遍历器中
    tuplesInPage:利用heapPage中实现的的遍历器来逐个遍历一个page中的tuple
    params:TransactionId暂时不用处理
    pagePos:page的位置 从0开始
     */
    public class HeapFileIterator implements DbFileIterator {


        private TransactionId tid;
        private Iterator<Tuple> tuplesInPage;
        private int pagePos = 0;



        public HeapFileIterator(TransactionId tid) {
            this.tid = tid;
        }

        public Iterator<Tuple> getTuplesInPage(PageId pid) throws TransactionAbortedException, DbException {
            HeapPage page = (HeapPage) Database.getBufferPool().getPage(tid, pid, Permissions.READ_ONLY);
            return page.iterator();
        }


        @Override
        public void open() throws DbException, TransactionAbortedException {
            pagePos = 0;
            HeapPageId heapPageId = new HeapPageId(getId(),pagePos);
            tuplesInPage = getTuplesInPage(heapPageId);
        }

        /**
        这里要注意当前页面的没有符合条件的tuple时还不能返回
        要判断是否还有剩余页面未遍历，还有的话继续处理下一个page
         */
        @Override
        public boolean hasNext() throws DbException, TransactionAbortedException {
            if(tuplesInPage==null)
                return false;
            if(!tuplesInPage.hasNext())
            {
                if(pagePos<numPages()-1)
                {
                    pagePos++;
                    HeapPageId heapPageId = new HeapPageId(getId(),pagePos);
                    tuplesInPage = getTuplesInPage(heapPageId);
                    return hasNext();
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return true;
            }

        }

        //使用tuplesInPage的next()
        @Override
        public Tuple next() throws DbException, TransactionAbortedException, NoSuchElementException {
            if(tuplesInPage==null)
                throw new NoSuchElementException();
            return tuplesInPage.next();
        }

        //初始化遍历器指向初始处
        //再调用一次open()等于初始化
        @Override
        public void rewind() throws DbException, TransactionAbortedException {
            open();
        }

        //关闭遍历器
        @Override
        public void close() {
             pagePos = 0;
             tuplesInPage = null;
        }
    }


}

